package com.tmt.datatable.ext.controller;

import com.tmt.datatable.TableRequest;
import com.tmt.datatable.TableResponse;
import com.tmt.datatable.editor.EditorModel;
import com.tmt.datatable.editor.EditorValid;
import com.tmt.datatable.ext.controller.impl.LoopReferenceBreakerImpl;
import com.tmt.helper.ReflectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceUnitUtil;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * 数据表格控制器
 * Created by Stark on 2017/3/2.
 */
public abstract class DataTableController<T extends Serializable,  ID extends Serializable> {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping({"", "/", "index"})
    public String indexPage(Model model) {
        return getIndexPage();
    }

    @ResponseBody
    @RequestMapping("/list/data")
    public TableResponse getTableData(TableRequest tRequest, Model model)
            throws ExecutionException, InterruptedException {
        return getTableResponse(tRequest, model, null, tRequest.getSearch().getSearcher(),
                tRequest.getFilter(), new HashMap<>()).get();
    }

    @ResponseBody
    @RequestMapping("/list/action")
    public EditorModel<T> doTableAction(@Valid @EditorValid EditorModel<T> editor)
            throws ExecutionException, InterruptedException {
        return  executeTableAction(editor, null).get();
    }

    /**
     * 获取Select2组件的下拉选项
     * @param request
     * @param id 指定返回的的Select2选择项的value值应该填入对象的哪个属性的值。默认填入对象的ID值。
     * @param search 在前端设定的可搜索字段范围内，进行字段值匹配，各个字段值匹配条件间是或的关系。
     * @param propName 指定要获取的选择项集合是对象数据集中某个属性值的集合。
     * @param tag 前端特殊查询的自定义标识。
     * @param pageIndex
     * @param pageSize
     * @return
     */
    @ResponseBody
    @RequestMapping("/select2")
    public SelectOptions queryForSelect2(HttpServletRequest request,
                                         @RequestParam(value = "id", required = false) String id,
                                         @RequestParam(value = "displayExtend", required = false) Boolean displayExtend,
                                         @RequestParam(value = "search", required = false) String search,
                                         @RequestParam(value = "prop", required = false) String propName,
                                         @RequestParam(value = "reqTag", required = false) String tag,
                                         @RequestParam(value = "page", defaultValue = "0") Integer pageIndex,
                                         @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize)
            throws ExecutionException, InterruptedException {
        Pageable pageable = new PageRequest(pageIndex, pageSize);
        Map<String, String[]> map =  request.getParameterMap();

        Map<String, String[]> map1 = new HashMap<>();
        List<String> list = Arrays.asList("id", "search", "prop", "displayExtend", "reqTag", "page", "pageSize");
        map.keySet().stream().filter(x-> !list.contains(x)).forEach(x-> map1.put(x, map.get(x)));

        displayExtend = displayExtend == null ? true : displayExtend;

        Class<T> targetClass = getEntityClass();
        return getMainService().findForSelect2(targetClass, id, propName, displayExtend, search, tag, pageable, map1, new HashMap<>(), map).get();
    }

    /********************************Protected Method********************************************/

    /**
     *
     * @param tRequest
     * @param model
     * @return
     */
    @Async
    protected Future<TableResponse<T>> getTableResponse(TableRequest tRequest, Model model,
                                                       String[] lazyProperties,
                                                       Map<String, String[]> searchCondition,
                                                       Map<String, String[]> filterConditions,
                                                       Map<String, String[]> limitedConditions)
            throws ExecutionException, InterruptedException {
        TableResponse<T> result = getMainService().queryByRequest(tRequest, getEntityClass(),
                null, lazyProperties, searchCondition, filterConditions, limitedConditions).get();

        getMainService().clearProperties(getEntityClass(), result);

        EntityManager em = getMainService().getRepo().getEntityManager();
        PersistenceUnitUtil unitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
        LoopReferenceBreakerImpl breaker = new LoopReferenceBreakerImpl();
        result.getData().parallelStream().forEach(x-> breaker.breakReference(x, unitUtil));
        return new AsyncResult<>(result);
    }

    /**
     *
     * @param editor
     * @return
     */
    @Async
    protected Future<EditorModel<T>> executeTableAction(EditorModel<T> editor, String[] lazyProperties){
        if ((EditorModel.ACTION_CREATE.equals(editor.getAction()) || EditorModel.ACTION_EDIT.equals(editor.getAction()))
                && editor.getFieldErrors() != null && editor.getFieldErrors().size() > 0) {
            editor.setError("输入信息存在错误，请仔细检查");
            return new AsyncResult<>(editor);
        }
        try {
            getMainService().executeTableAction(editor, lazyProperties);
        }catch (Exception ex){
            //ex.printStackTrace();
        }finally {
            EntityManager em = getMainService().getRepo().getEntityManager();
            PersistenceUnitUtil unitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
            LoopReferenceBreakerImpl breaker = new LoopReferenceBreakerImpl();
            editor.getData().parallelStream().forEach(x -> breaker.breakReference(x, unitUtil));
        }
        return new AsyncResult<>(editor);
    }

    /********************************Private Method********************************************/

    /**
     *
     * @return
     */
    private ITmtBaseService<T,ID> getMainService(){
        ITmtBaseService<T,ID> result = null;
        Class<T> targetClazz = getEntityClass();
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                ParameterizedType type = (ParameterizedType)field.getType().getGenericSuperclass();
                if (ITmtBaseService.class.isAssignableFrom((Class)type.getRawType()) && type.getActualTypeArguments()[0] == targetClazz){
                    try {
                        //作为public字段直接读取值。
                        result = (ITmtBaseService<T, ID>) field.get(this);
                    }catch (Exception ex){}

                    //如果不是public字段，则通过get方法获取值
                    if (result == null){
                        result = (ITmtBaseService<T,ID>) ReflectHelper.getFieldValue(this, field.getName());
                    }
                    break;
                }
            }catch (Exception ex){}
        }
        return result;
    }

    /**
     *
     * @return
     */
    private String getIndexPage() {
        String tablePage = null;
        try {
            TablePage annotation = this.getClass().getAnnotation(TablePage.class);
            tablePage = annotation.value();
        }catch (Exception ex){
            logger.error(MessageFormat.format("{0}子类必须在类上添加{1}注释",
                    DataTableController.class.getSimpleName(), TablePage.class.getSimpleName()));
        }
        return tablePage;
    }

    /**
     *
     * @return
     */
    private Class<T> getEntityClass(){
        Type type = this.getClass().getGenericSuperclass();
        return (Class<T>)((ParameterizedType)type).getActualTypeArguments()[0];
    }
}
